package geometry;

/**
 * Represents a geometric angle and provides static operations for angles.
 *
 * @author Catastrophe
 */
public final class Angle implements Cloneable
{
	/**
	 * The constant Pi.  A complete circle is considered to be an angle of 2 * Pi radians.
	 */
	public static final double PI = Math.PI;

	/**
	 * The number of radians in a complete circle.
	 */
	public static final double RADIAN = 2.0 * PI;

	/**
	 * The number of degrees in a complete circle.
	 */
	public static final double DEGREE = 360.0;

	/**
	 * The number of gradians in a full circle.
	 */
	public static final double GRADIAN = 400.0;

	/**
	 * The number of hours in a complete circle.
	 */
	public static final double HOUR = 12.0;

	/**
	 * The number of points in a complete circle.
	 */
	public static final double POINT = 32.0;

	/**
	 * @param radians the angle in radians.
	 * @return the corresponding angle in degrees.
	 */
	public static double radiansToDegrees(double radians)
	{
		return radians * DEGREE / RADIAN;
	}

	/**
	 * @param radians the angle in radians.
	 * @return the corresponding angle in gradians.
	 */
	public static double radiansToGradians(double radians)
	{
		return radians * GRADIAN / RADIAN;
	}

	/**
	 * @param radians the angle in radians.
	 * @return the corresponding angle in hours.
	 */
	public static double radiansToHours(double radians)
	{
		return radians * HOUR / RADIAN;
	}

	/**
	 * @param radians the angle in radians.
	 * @return the corresponding angle in points.
	 */
	public static double radiansToPoints(double radians)
	{
		return radians * POINT / RADIAN;
	}

	/**
	 * @param degrees the angle in degrees.
	 * @return the corresponding angle in radians.
	 */
	public static double degreesToRadians(double degrees)
	{
		return degrees * RADIAN / DEGREE;
	}

	/**
	 * @param degrees the angle in degrees.
	 * @return the corresponding angle in gradians.
	 */
	public static double degreesToGradians(double degrees)
	{
		return degrees * GRADIAN / DEGREE;
	}

	/**
	 * @param degrees the angle in degrees.
	 * @return the corresponding angle in hours.
	 */
	public static double degreesToHours(double degrees)
	{
		return degrees * HOUR / DEGREE;
	}

	/**
	 * @param degrees the angle in degrees.
	 * @return the corresponding angle in points.
	 */
	public static double degreesToPoints(double degrees)
	{
		return degrees * POINT / DEGREE;
	}

	/**
	 * @param gradians the angle in gradians.
	 * @return the corresponding angle in radians.
	 */
	public static double gradiansToRadians(double gradians)
	{
		return gradians * RADIAN / GRADIAN;
	}

	/**
	 * @param gradians the angle in gradians.
	 * @return the corresponding angle in degrees.
	 */
	public static double gradiansToDegrees(double gradians)
	{
		return gradians * DEGREE / GRADIAN;
	}

	/**
	 * @param gradians the angle in gradians.
	 * @return the corresponding angle in hours.
	 */
	public static double gradiansToHours(double gradians)
	{
		return gradians * HOUR / GRADIAN;
	}

	/**
	 * @param gradians the angle in gradians.
	 * @return the corresponding angle in points.
	 */
	public static double gradiansToPoints(double gradians)
	{
		return gradians * POINT / GRADIAN;
	}

	/**
	 * @param hours the angle in hours.
	 * @return the corresponding angle in radians.
	 */
	public double hoursToRadians(double hours)
	{
		return hours * RADIAN / HOUR;
	}

	/**
	 * @param hours the angle in hours.
	 * @return the corresponding angle in degrees.
	 */
	public double hoursToDegrees(double hours)
	{
		return hours * DEGREE / HOUR;
	}

	/**
	 * @param hours the angle in hours.
	 * @return the corresponding angle in gradians.
	 */
	public double hoursToGradians(double hours)
	{
		return hours * GRADIAN / HOUR;
	}

	/**
	 * @param hours the angle in hours.
	 * @return the corresponding angle in points.
	 */
	public double hoursToPoints(double hours)
	{
		return hours * POINT / HOUR;
	}

	/**
	 * @param points the angle in points.
	 * @return the corresponding angle in radians.
	 */
	public double pointsToRadians(double points)
	{
		return points * RADIAN / POINT;
	}

	/**
	 * @param points the angle in points.
	 * @return the corresponding angle in degrees.
	 */
	public double pointsToDegrees(double points)
	{
		return points * DEGREE / POINT;
	}

	/**
	 * @param points the angle in points.
	 * @return the corresponding angle in gradians.
	 */
	public double pointsToGradians(double points)
	{
		return points * GRADIAN / POINT;
	}

	/**
	 * @param points the angle in points.
	 * @return the corresponding angle in hours.
	 */
	public double pointsToHours(double points)
	{
		return points * HOUR / POINT;
	}

	/**
	 * @return a right angle.  A right angle is an angle of 90 degrees.
	 */
	public static Angle rightAngle()
	{
		return new Angle(PI / 2);
	}

	/**
	 * @return a straight angle.  A straight angle is an angle of 180 degrees.
	 */
	public static Angle straightAngle()
	{
		return new Angle(PI);
	}

	/**
	 * This angle's radian measure.
	 */
	private double angle;

	/**
	 * Creates a new Angle.
	 *
	 * @param arcLength the length of the angle's arc.
	 * @param radius the radius from the vertex of the angle to the arc.
	 */
	public Angle(double arcLength, double radius)
	{
		this(arcLength / radius);
	}

	/**
	 * Creates a new Angle.
	 *
	 * @param radians the angle to form, in radians.
	 */
	public Angle(double radians)
	{
		angle = radians;
	}

	/**
	 * Creates a new Angle set at zero.
	 */
	public Angle()
	{
		this(0.0);
	}

	/**
	 * @return this angle, in radians.
	 */
	public double getAngleRadians()
	{
		return angle;
	}

	/**
	 * @return this angle, in degrees.
	 */
	public double getAngleDegrees()
	{
		return radiansToDegrees(getAngleDegrees());
	}

	/**
	 * @return this angle, in gradians.
	 */
	public double getAngleGradians()
	{
		return radiansToGradians(getAngleRadians());
	}

	/**
	 * @return this angle, in hours.
	 */
	public double getAngleHours()
	{
		return radiansToHours(getAngleRadians());
	}

	/**
	 * @return this angle, in points.
	 */
	public double getAnglePoints()
	{
		return radiansToPoints(getAngleRadians());
	}

	/**
	 * Normalizes this Angle.  A normalized angle is between no angle and infinitely close to a full circle.
	 */
	public void normalize()
	{
		while(angle > RADIAN)
			angle -= RADIAN;
		while(angle < -RADIAN)
			angle += RADIAN;
	}

	/**
	 * @return the cosine of this Angle.
	 */
	public double cos()
	{
		return Math.cos(getAngleRadians());
	}

	/**
	 * @return the sine of this Angle.
	 */
	public double sin()
	{
		return Math.sin(getAngleRadians());
	}

	/**
	 * @return the tangent of this Angle.
	 */
	public double tan()
	{
		return Math.tan(getAngleRadians());
	}

	/**
	 * Inverts this Angle.  The inverse of an angle is the angle which, when added to this angle, results in an angle of zero.
	 */
	public void invert()
	{
		angle = -angle;
	}

	/**
	 * Adds an Angle to this Angle.
	 *
	 * @param other the Angle to add to this Angle.
	 */
	public void add(Angle other)
	{
		angle += other.getAngleRadians();
	}

	/**
	 * Subtracts an Angle from this Angle.
	 *
	 * @param other the Angle to subtract from this Angle.
	 */
	public void subtract(Angle other)
	{
		angle -= other.getAngleRadians();
	}

	/**
	 * Multiplies this Angle by the specified value.
	 *
	 * @param value the value to multiply this Angle by.
	 */
	public void multiply(double value)
	{
		angle *= value;
	}

	/**
	 * @return the complement of this Angle.  An angle's complement is the angle that, when added to the angle, creates a 90 degree angle.
	 */
	public Angle getComplement()
	{
		return new Angle(PI - getAngleRadians());
	}

	/**
	 * @return the supplement of this Angle.  An angle's supplement is the angle that, when added to the angle, creates a 180 degree angle.
	 */
	public Angle getSupplement()
	{
		return new Angle(PI / 2 - getAngleRadians());
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	@Override
	public Angle clone()
	{
		return new Angle(angle);
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object other)
	{
		return other instanceof Angle ? equals((Angle)other) : false;
	}

	/**
	 * Tests two angles for equivalence.  Angles are equivalent if they are identical when normalized.
	 *
	 * @param other the Angle to check for equivalence.
	 * @return <code>true</code> if the specified Angle is equivalent to this Angle or <code>false</code> otherwise.
	 */
	public boolean equals(Angle other)
	{
		return other == null ? false : getAngleRadians() == other.getAngleRadians();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode()
	{
		return new Double(getAngleRadians()).hashCode() * 3 + 5;
	}
}